package midas.mw.crt;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.ByteBuffer;
import java.util.Properties;
import java.util.Vector;

import midas.mw.cfg.CfgError;
import midas.mw.cfg.CfgLogging;
import midas.mw.cfg.log.Logger;
import midas.mw.crt.common.CBMessage;
import midas.mw.crt.common.IPMessage;
import midas.mw.crt.interfaces.Crt;
import midas.mw.crt.interfaces.CrtCallback;
import midas.mw.interfaces.Component;
import midas.mw.interfaces.Mw;

// Queue class requires Java 1.5
//--import java.util.Queue;
//--import java.util.concurrent.BlockingQueue;
//--import java.util.concurrent.LinkedBlockingQueue;

public class CbrImpl implements Component
{
	private static Mw middlewareInterface = null;
	private static CrtCallback CBRcallback = null;
	private int localNodeID = -1;
	//	private BlockingQueue<CBMessage> blockingQueue = null;
	private QueueEmulate blockingQueue = null;
	private QueueEmulate queueToCnServer = null;
	private QueueEmulate queueToNodes = null;
	
	Thread cNServerThread;
	Thread cNClientThread;
	
	public CbrImpl()
	{
//		blockingQueue = new LinkedBlockingQueue<CBMessage>();
		blockingQueue = new QueueEmulate();
		queueToCnServer = new QueueEmulate();
		queueToNodes = new QueueEmulate();
/*
		String str = new String("d*pa!");
		byte[] payload = str.getBytes();
		CBMessage msg = new CBMessage("Policeman", payload, 36);
		putCBMessage(msg);
*/
	}

	public void setLocalNodeID(int id)
	{
		if (id != localNodeID)
			localNodeID = id;
	}

	public int getLocalNodeID()
	{
		return localNodeID;
	}

	// enqueue the new message on the queue
	public void putCBMessage(CBMessage msg)
	{
//		try {
//			blockingQueue.put(msg);
//		} catch (InterruptedException e) {
		// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
//		synchronized (blockingQueue) {
			blockingQueue.enqueue(msg);
//		}
	}

	// check if there are any messages queued
	public boolean checkCBMessage()
 	{
//		return !blockingQueue.isEmpty();
		return !blockingQueue.isEmpty();
 	}

	// dequeue the top-most message from the queue (may block)
	public CBMessage getCBMessage()
	{
		CBMessage msg = null;

//		try {
//			msg = blockingQueue.take();
//		} catch (InterruptedException e) {
		// TODO Auto-generated catch block
//			e.printStackTrace();
//		}

		try {
			msg = (CBMessage)blockingQueue.dequeue();
		} catch(InterruptedException ex) {}

		return msg;
 	}


	public void sendMessage(String destination, byte[] payload, int flags)
	{
		CBMessage msg = new CBMessage(destination, payload, flags);
		String payload_str = new String(payload);

		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT.CbrImpl", CfgLogging.SEV_DEBUG, "sendMessage()");
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrImpl", CfgLogging.SEV_DEBUG, "CBR message = " + msg);
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrImpl", CfgLogging.SEV_DEBUG, " destination: " + destination);
		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrImpl", CfgLogging.SEV_DEBUG, " payload: " + payload_str);

		putCBMessage(msg);
		
	}
 
//	public void waitForMessage();

	public void incomingMessage(byte b[])
	{

			CBRcallback.onReceivedMessage(b);

	}

	public void registerCBRCallback(CrtCallback crtCallback)
	{
		CBRcallback = crtCallback;
	}

	
	
	
	public void sendMessage(IPMessage ipMessage)
	{					
	}

	public String getOwnNodeId()
	{
		return "";
	}
	
	public Vector getNeighbourhoodInfo(int minNumHops, int MaxNumHops)
	{			
		return new Vector();
	}
	

	
	public int stop()	
	{	
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CbrImpl.stop()");		
		if (cNServerThread!=null){
			cNServerThread.stop();
		}
		if (cNClientThread!=null){
			cNClientThread.stop();
		}
		return 0;
	}
	
	public Mw getMiddlewareInterface()
	{
		return middlewareInterface;
	}
	
	private void configure() throws CrtException
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, "CRT", 0, "configure()");
		
		Properties middlewareProperties;

	    /* Loading the properties file
	     */
	    try 
	    {	    	
	    	FileInputStream file = new FileInputStream(Constants.CONFIGURATION_FILE);

	    	middlewareProperties = new Properties();
	    	middlewareProperties.load(file);
	    	file.close();
	    	
	    	//Fixing the variables	    	
	    		    		
	    } catch (FileNotFoundException fnfe) {
	    	CfgError.errorReporting(CfgError.ERR_OPER, "CRT", CfgError.SEV_FATAL, 
					"configure(). Failed to get the Properties file '" + 
					fnfe.getMessage() + "'", null);
	      	throw new CrtException ();
	    } catch (IOException ioe) {
	    	CfgError.errorReporting(CfgError.ERR_OPER, "CRT", CfgError.SEV_FATAL, 
					"configure(). Failed to read the Properties file '" + 
					ioe.getMessage() + "'", null);
	    	throw new CrtException ();
	    }	    
	}

	public void sendMessageToCentralNode(byte[] msg)
	{
		// CBR node id + message
		byte[] b = new byte[4 + msg.length];
		int i = localNodeID; 

		b[0] = (byte)((i & 0xff000000) >> 24);
		b[1] = (byte)((i & 0xff0000) >> 16);
		b[2] = (byte)((i & 0xff00) >> 8);
		b[3] = (byte)((i & 0xff));

		System.arraycopy(msg, 0, b, 4, msg.length);

		synchronized (queueToCnServer) {
			queueToCnServer.enqueue(b);
			queueToCnServer.notifyAll();
		}
	}

	public void sendMessageToNodes(CBMessage msg){
		synchronized (queueToNodes) {
			msg.setTimestamp();
			queueToNodes.enqueue(msg);
		}
	}

	public int start(Mw mwint)
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CbrImpl.start()");

		middlewareInterface = mwint;

		Crt crt = (Crt)this.getMiddlewareInterface().getRunningComponent("midas.mw.crt.CrtImpl");
		int incomingPort = ((CrtImpl)crt).getConstants().getCBRServerPort();
		String incomingIP = ((CrtImpl)crt).getConstants().getCBRServerIP();
		int fmodePort = ((CrtImpl)crt).getConstants().getCBRFModePort();
		String fmodeIP = ((CrtImpl)crt).getConstants().getCBRFModeIP();
		int id = ((CrtImpl)crt).getConstants().getCBRNodeID();
		setLocalNodeID(id);

		if (id == -1) {
			CfgError.errorReporting(CfgError.ERR_OPER, "CbrImpl", CfgError.SEV_FATAL, "localNodeID is not initialized properly (please set CBRNodeID in ConfigurationCRT.properties)", null); 
			return -1;
		}

		CfgLogging.logEvent(CfgLogging.LOG_INFO, "CRT.CbrImpl", CfgLogging.SEV_INFORMAL, "localNodeID: " + id);

		// Create the object with the run() method
		Runnable runnable = new CrtServer(this, incomingPort,incomingIP);

		// Create the thread supplying it with the runnable object
		Thread thread = new Thread(runnable);

		// Start the thread
		thread.start();

		// CRT -> CBR communication
//		int outgoingPort = ((CrtImpl)crt).getConstants().getCBRServerPort2();
		int outgoingPort = incomingPort+1; 

		Runnable outgoing = new CrtServer(this, outgoingPort, incomingIP);
		Thread outgoingThread = new Thread(outgoing);
		outgoingThread.start();

		if (false) {
			CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CbrImpl started in \"testing mode\".");
			cNServerThread = new Thread(new CBRCentralNodeServer(this, fmodePort, fmodeIP, queueToNodes));
			cNServerThread.start();	
			cNClientThread = new Thread (new CBRCentralNodeClient(this, ((CrtImpl)crt).getConstants().getCentralNodeIP(), fmodePort, queueToCnServer));
			cNClientThread.start();	
			this.setLocalNodeID(333);
			this.sendMessageToNodes(new CBMessage("333", "DDDD4".getBytes(), 0));
			this.sendMessageToNodes(new CBMessage("-1", "EEE5".getBytes(), 0));
			this.sendMessageToNodes(new CBMessage("-1", "FFFFF6".getBytes(), 0));
			this.sendMessageToNodes(new CBMessage("-1", "GGGGGGG7".getBytes(), 0));
			this.sendMessageToCentralNode("AAAAA1".getBytes());
			this.sendMessageToCentralNode("BBB2".getBytes());
			this.sendMessageToCentralNode("CCCC3".getBytes());
		} else
		if (((CrtImpl)crt).getConstants().NODE_TYPE ==0){
			//START CENTRAL NODE SERVER
			cNServerThread = new Thread(new CBRCentralNodeServer(this, fmodePort, fmodeIP, queueToNodes));
			cNServerThread.start();	
		}else if (((CrtImpl)crt).getConstants().NODE_TYPE ==2){
			//START GATEWAY NODE CLIENT (TO CN SERVER)
			cNClientThread = new Thread (new CBRCentralNodeClient(this, ((CrtImpl)crt).getConstants().getCentralNodeIP(), fmodePort, queueToCnServer));
			cNClientThread.start();
		}

		//Do nothing for normal nodes
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "CbrImpl started.");
		return 0;
	}

}
